﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Linq;
using Unity.VisualScripting;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

public struct PuzzleObject
{
    public uint Type;
    public Texture2D Texture;
    public Sprite Sprite;
}

enum ObjectState: uint
{ 
    None    = 0,
    Block   = 1 << 0,
    Win     = 1 << 1,
    Box     = 1 << 2,


    Me      = (uint)1 << 31
}

public enum InputType
{ 
    None,
    Up,
    Down, 
    Left, 
    Right,
    Back,
    Reset
}

public class StaticMapData
{
    /// <summary>
    /// 从单个字符映射到对应的哪种Object类型
    /// </summary>
    public Dictionary<char, string> SymbolToNameDict = new Dictionary<char, string>();
    /// <summary>
    /// 从名字映射到定义的Object属性
    /// </summary>
    public Dictionary<string, PuzzleObject> NameToAttrDict = new Dictionary<string, PuzzleObject>();
    /// <summary>
    /// 初始地图状态
    /// </summary>
    public StaticMapState StaticMapState;
    /// <summary>
    /// 解析出来的规则
    /// </summary>
    public PuzzleRuleSystem RuleSystem = new PuzzleRuleSystem();

    public bool SymbolToName(in char Symbol, out string Name)
    {
        var bHasName = SymbolToNameDict.TryGetValue(Symbol, out Name);
        if (bHasName == false)
        {
            Debug.LogError(Symbol + " can not Find in SymbolToNameDict");
            return false;
        }
        return true;
    }
    public bool NameToAttr(in string Name, out PuzzleObject Attr)
    {
        var bHasAttr = NameToAttrDict.TryGetValue(Name, out Attr);
        if (bHasAttr == false)
        {
            Debug.LogError(Name + " can not Find in NameToAttrDict");
            return false;
        }
        return true;
    }
    public void DebugInitState()
    {
        SymbolToNameDict.Clear();
        NameToAttrDict.Clear();

        {
            SymbolToNameDict.Add('#', "Wall");
            SymbolToNameDict.Add('.', "Ground");
            SymbolToNameDict.Add('@', "Target");
            SymbolToNameDict.Add('$', "Target and Box");
            SymbolToNameDict.Add('%', "Box");
            SymbolToNameDict.Add('_', "Box2");
            SymbolToNameDict.Add('*', "Player");
        }

        {
            PuzzleObject puzzleObject = new PuzzleObject();
            puzzleObject.Texture = new Texture2D(1, 1);
            puzzleObject.Texture.SetPixel(0, 0, Color.gray);
            puzzleObject.Texture.filterMode = FilterMode.Point;
            puzzleObject.Texture.Apply();
            puzzleObject.Sprite = Sprite.Create(puzzleObject.Texture, new Rect(0, 0, 1, 1), new Vector2(0, 0), 1f);
            NameToAttrDict.Add("Ground", puzzleObject);
        }

        {
            PuzzleObject puzzleObject = new PuzzleObject();
            puzzleObject.Type = (uint)ObjectState.Block;
            puzzleObject.Texture = new Texture2D(1, 1);
            puzzleObject.Texture.SetPixel(2, 2, new Color(1.0f, 0.5f, 0.2f));
            puzzleObject.Texture.filterMode = FilterMode.Point;
            puzzleObject.Texture.Apply();
            puzzleObject.Sprite = Sprite.Create(puzzleObject.Texture, new Rect(0, 0, 1, 1), new Vector2(0, 0), 1f);
            NameToAttrDict.Add("Wall", puzzleObject);
        }

        {
            PuzzleObject puzzleObject = new PuzzleObject();
            puzzleObject.Type = (uint)ObjectState.Win;
            puzzleObject.Texture = new Texture2D(5, 5);
            puzzleObject.Texture.SetPixel(2, 2, Color.yellow);
            puzzleObject.Texture.filterMode = FilterMode.Point;
            puzzleObject.Texture.Apply();
            puzzleObject.Sprite = Sprite.Create(puzzleObject.Texture, new Rect(0, 0, 5, 5), new Vector2(0, 0), 5f);
            NameToAttrDict.Add("Target", puzzleObject);
        }

        {
            PuzzleObject puzzleObject = new PuzzleObject();
            puzzleObject.Type = (uint)ObjectState.Me;
            puzzleObject.Texture = new Texture2D(5, 5);
            puzzleObject.Texture.SetPixel(2, 2, Color.cyan);
            puzzleObject.Texture.filterMode = FilterMode.Point;
            puzzleObject.Texture.Apply();
            puzzleObject.Sprite = Sprite.Create(puzzleObject.Texture, new Rect(0, 0, 5, 5), new Vector2(0, 0), 5f);
            NameToAttrDict.Add("Player", puzzleObject);
        }

        {
            PuzzleObject puzzleObject = new PuzzleObject();
            puzzleObject.Type = (uint)ObjectState.Box;
            puzzleObject.Texture = new Texture2D(5, 5);
            puzzleObject.Texture.SetPixel(0, 0, Color.green);
            puzzleObject.Texture.SetPixel(0, 1, Color.green);
            puzzleObject.Texture.SetPixel(0, 2, Color.green);
            puzzleObject.Texture.SetPixel(0, 3, Color.green);
            puzzleObject.Texture.SetPixel(0, 4, Color.green);
            puzzleObject.Texture.SetPixel(1, 0, Color.green);
            puzzleObject.Texture.SetPixel(1, 4, Color.green);
            puzzleObject.Texture.SetPixel(2, 0, Color.green);
            puzzleObject.Texture.SetPixel(2, 4, Color.green);
            puzzleObject.Texture.SetPixel(3, 0, Color.green);
            puzzleObject.Texture.SetPixel(3, 4, Color.green);
            puzzleObject.Texture.SetPixel(4, 0, Color.green);
            puzzleObject.Texture.SetPixel(4, 1, Color.green);
            puzzleObject.Texture.SetPixel(4, 2, Color.green);
            puzzleObject.Texture.SetPixel(4, 3, Color.green);
            puzzleObject.Texture.SetPixel(4, 4, Color.green);
            puzzleObject.Texture.filterMode = FilterMode.Point;
            puzzleObject.Texture.Apply();
            puzzleObject.Sprite = Sprite.Create(puzzleObject.Texture, new Rect(0, 0, 5, 5), new Vector2(0, 0), 5f);
            NameToAttrDict.Add("Box", puzzleObject);
        }

        {
            PuzzleObject puzzleObject = new PuzzleObject();
            puzzleObject.Type = (uint)ObjectState.Box;
            puzzleObject.Texture = new Texture2D(5, 5);
            puzzleObject.Texture.SetPixel(0, 0, Color.red);
            puzzleObject.Texture.SetPixel(0, 1, Color.red);
            puzzleObject.Texture.SetPixel(0, 2, Color.red);
            puzzleObject.Texture.SetPixel(0, 3, Color.red);
            puzzleObject.Texture.SetPixel(0, 4, Color.red);
            puzzleObject.Texture.SetPixel(1, 0, Color.red);
            puzzleObject.Texture.SetPixel(1, 4, Color.red);
            puzzleObject.Texture.SetPixel(2, 0, Color.red);
            puzzleObject.Texture.SetPixel(2, 4, Color.red);
            puzzleObject.Texture.SetPixel(3, 0, Color.red);
            puzzleObject.Texture.SetPixel(3, 4, Color.red);
            puzzleObject.Texture.SetPixel(4, 0, Color.red);
            puzzleObject.Texture.SetPixel(4, 1, Color.red);
            puzzleObject.Texture.SetPixel(4, 2, Color.red);
            puzzleObject.Texture.SetPixel(4, 3, Color.red);
            puzzleObject.Texture.SetPixel(4, 4, Color.red);
            puzzleObject.Texture.filterMode = FilterMode.Point;
            puzzleObject.Texture.Apply();
            puzzleObject.Sprite = Sprite.Create(puzzleObject.Texture, new Rect(0, 0, 5, 5), new Vector2(0, 0), 5f);
            NameToAttrDict.Add("Box2", puzzleObject);
        }

        {
            StaticMapState.SizeX = 9;
            StaticMapState.SizeY = 9;
            StaticMapState.Map = new char[81] {
                '#', '#', '#', '#', '#', '#', '#','#','#',
                '#', '.', '.', '.', '.', '.', '.','.','#',
                '#', '.', '.', '.', '@', '.', '.','.','#',
                '#', '.', '.', '_', '%', '*', '.','.','#',
                '#', '.', '.', '.', '.', '.', '.','.','#',
                '#', '.', '.', '.', '.', '.', '.','.','#',
                '#', '.', '.', '.', '.', '.', '.','.','#',
                '#', '.', '.', '.', '.', '.', '.','.','#',
                '#', '#', '#', '#', '#', '#', '#','#','#',
            };
        }

        RuleSystem.Generate();
        RuleSystem.Gen();
        RuleSystem.DumpStateMachine();
    }
}

public class PuzzleBackend : MonoBehaviour
{
    StaticMapData StaticMapData = new StaticMapData();
    /// <summary>
    /// 运行时地图状态，用栈表示，可以复原
    /// </summary>
    Stack<MapState> RuntimeMapStack;

    bool bIsInputDirty = false;

    // Start is called before the first frame update
    void Start()
    {
        bIsInputDirty = false;
        ClearMap();
        StaticMapData.DebugInitState();
        RuntimeMapStack = new Stack<MapState>(); 
        RuntimeMapStack.Push(MapState.GenerateByStatic(StaticMapData));
        GenerateMap_Internal(RuntimeMapStack.Peek());
    }

    // Update is called once per frame
    void Update()
    {
        if (bIsInputDirty == false)
        {
            return;
        }

        ClearMap();
        GenerateMap_Internal(RuntimeMapStack.Peek());

        bIsInputDirty = false;
    }

    public void DealInput(InputType InInputType)
    {
        if (InInputType == InputType.None)
        {
            return;
        }
        else if (InInputType == InputType.Back)
        {
            if (RuntimeMapStack.Count > 1)
            {
                RuntimeMapStack.Pop();
                bIsInputDirty = true;
            }
            return;
        }
        else if (InInputType == InputType.Reset)
        {
            RuntimeMapStack.Push(MapState.GenerateByStatic(StaticMapData));
            bIsInputDirty = true;
            return;
        }
        else if (InInputType == InputType.Left || InInputType == InputType.Right || InInputType == InputType.Up || InInputType == InputType.Down)
        {
            RuntimeMapStack.Peek().RefreshStatus();
            RuntimeMapStack.Push(PuzzleSolver.Solve(RuntimeMapStack.Peek(), StaticMapData.RuleSystem, InInputType));
            bIsInputDirty = true;
        }
    }

    public void GenerateMap() 
    {
        ClearMap();
        StaticMapData.DebugInitState();
        GenerateMap_Internal(StaticMapData.StaticMapState);
    }

    /// <summary>
    /// 清除原始地图
    /// </summary>
    void ClearMap()
    {
        var Counter = 0;
        GameObject PuzzleRoot;
        while ((PuzzleRoot = GameObject.Find("PuzzleRoot")) && Counter < 10)
        {
            Counter++;
            DestroyImmediate(PuzzleRoot);
        }

        if (Counter == 10) 
        {
            Debug.LogWarning("DeadLoop Detact");
        }
    }

    void GenerateMap_Internal(StaticMapState CurrentMapState)
    {
        var SquareSize = 1.0f;
        var PuzzleRoot = new GameObject("PuzzleRoot");

        for (int i = 0; i < CurrentMapState.SizeY; i++) 
        { 
            for (int j = 0; j < CurrentMapState.SizeX; j++)
            {
                var Index = (CurrentMapState.SizeY - i - 1) * CurrentMapState.SizeX + j;

                var Symbol = CurrentMapState.Map[Index];

                string Name;
                var bHasName = StaticMapData.SymbolToNameDict.TryGetValue(Symbol, out Name);
                if (bHasName == false)
                {
                    Debug.LogError(Symbol + " can not Find in Name Table");
                    return;
                }

                PuzzleObject Object;
                var bHasObject = StaticMapData.NameToAttrDict.TryGetValue(Name, out Object);
                if (bHasObject == false)
                {
                    Debug.LogError(Name + " can not Find in Object Table");
                    return;
                }

                var Location = new Vector3(
                    (j - (CurrentMapState.SizeX - 1) / 2.0f) * SquareSize,
                    (i - (CurrentMapState.SizeY - 1) / 2.0f) * SquareSize,
                    0
                );

                var TempActor = new GameObject(Name + Index.ToString());
                TempActor.transform.SetParent(PuzzleRoot.transform);
                TempActor.transform.localPosition = Location;

                SpriteRenderer TempRenderer = TempActor.AddComponent<SpriteRenderer>();
                TempRenderer.sprite = Object.Sprite;
            }
        }
    }

    void GenerateMap_Internal(MapState CurrentMapState)
    {
        var SquareSize = 1.0f;
        var PuzzleRoot = new GameObject("PuzzleRoot");

        // 画地板
        {
            PuzzleObject Object;
            var bHasObject = StaticMapData.NameToAttrDict.TryGetValue("Ground", out Object);
            if (bHasObject == false)
            {
                Debug.LogError("Ground" + " can not Find in Object Table");
                return;
            }

            for (int i = 0; i < CurrentMapState.SizeY; i++)
            {
                for (int j = 0; j < CurrentMapState.SizeX; j++)
                {
                    var Index = (CurrentMapState.SizeY - i - 1) * CurrentMapState.SizeX + j;

                    var Location = new Vector3(
                        (j - (CurrentMapState.SizeX - 1) / 2.0f) * SquareSize,
                        (i - (CurrentMapState.SizeY - 1) / 2.0f) * SquareSize,
                        10
                    );

                    var TempActor = new GameObject("Ground_" + (Index).ToString());
                    TempActor.transform.SetParent(PuzzleRoot.transform);
                    TempActor.transform.localPosition = Location;
                    SpriteRenderer TempRenderer = TempActor.AddComponent<SpriteRenderer>();
                    TempRenderer.sprite = Object.Sprite;
                }
            }
        }

        // 画Object
        for (int i = 0; i < CurrentMapState.SizeY; i++)
        {
            for (int j = 0; j < CurrentMapState.SizeX; j++)
            {
                var Index = (CurrentMapState.SizeY - i - 1) * CurrentMapState.SizeX + j;

                var Elements = CurrentMapState.Map[Index].Objects;

                foreach (var Element in Elements)
                {
                    var Name = Element.Name;

                    PuzzleObject Object;
                    var bHasObject = StaticMapData.NameToAttrDict.TryGetValue(Name, out Object);
                    if (bHasObject == false)
                    {
                        Debug.LogError(Name + " can not Find in Object Table");
                        return;
                    }

                    var Location = new Vector3(
                        (j - (CurrentMapState.SizeX - 1) / 2.0f) * SquareSize,
                        (i - (CurrentMapState.SizeY - 1) / 2.0f) * SquareSize,
                        0
                    );

                    var TempActor = new GameObject(Name + Index.ToString());
                    TempActor.transform.SetParent(PuzzleRoot.transform);
                    TempActor.transform.localPosition = Location;

                    SpriteRenderer TempRenderer = TempActor.AddComponent<SpriteRenderer>();
                    TempRenderer.sprite = Object.Sprite;

                    Element.ObjectRef = TempActor;
                }
            }
        }
    }
}
